#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "symtable.h"
#include "dd_dynamic_array.h"

// number of entries on each symtable
#define SYMMAX 100

#define symtable_index(table, index) ((table << 16) | index)

// symtable
struct symtable {
	struct entry entry[SYMMAX];
	int lastentry;
	int parent;
	int index;
} *symtable;

struct dd_dynamic_array symtable_array;
int current_symtable;

/* init
 * allocate memory
 * no symbols inside
 */
int symtable_init() {

	// init symtable and its index
	symtable = 0;
	current_symtable = -1;

	// init symtable array and push the first table
	dd_da_init(&symtable_array, sizeof(struct symtable));
	symtable_push();

	/* everything OK */
	return 1;
}

// lookup on a single table
int lookup_table(char s[], struct symtable *t) {
	for (int i = 0; i <= t->lastentry; i++) {
		if (strcmp(t->entry[i].lexptr, s) == 0) {
			return i;
		}
	}

	return -1;
}

// find symbol, and return its index
int lookup(char s[]) {

	struct symtable *csymtable = symtable;
	while (csymtable) {
		int index = lookup_table(s, csymtable);
		if (index >= 0) {
			return symtable_index(csymtable->index, index);
		}
		csymtable = dd_da_get(&symtable_array, csymtable->parent);
	}

	/* symbol not found */
	return -1;
}

// insert new symbol
int symtable_insert(char s[], int tok) {

	// entry to be returned
	struct entry *symentry;

	// entry already in sym table - return it
	int index = lookup(s);
	if (index >= 0) {
		return index;
	}

	// make sure sym table has space
	if (symtable->lastentry +1 >= SYMMAX) {
		printf("symbol table full");
		return -1;
	}

	// sym table about to get a new symbol - prepare it
	int len;
	len = strlen(s);

	// entry's index
	symtable->lastentry++;

	// create new entry
	symentry = &symtable->entry[symtable->lastentry];
	symentry->token = tok;
	symentry->lexptr = malloc(sizeof(char) *len +1);
	symentry->value = 0;
	strcpy(symentry->lexptr, s);

	return symtable_index(current_symtable, symtable->lastentry);
}

// clean current system table and all it's parents
void symtable_clean() {
	dd_da_free(&symtable_array);
}

// print symbol table
void symtable_print() {
	for (int i = 0; i < symtable_array.elements; i++) {

		printf("symtable %d\n", i);
		struct symtable *s = dd_da_get(&symtable_array, i);
		for (int i = 0; i <= s->lastentry; i++) {
			printf("\t%s | token: %d\n", s->entry[i].lexptr, s->entry[i].token);
		}

		if (s->parent >= 0) printf("parent: %d\n", s->parent);
		else printf("parent: none\n");
		printf("end symtable:\n");
	}
}

// get entry from index
struct entry *symtable_entryat(int index) {

	int symindex = index >> 16;
	index = 0xFF & index;

	struct symtable *t = dd_da_get(&symtable_array, symindex);

	/* index is within array's bounds - return entry at index */
	if (index >= 0 && index <= t->lastentry) {
		return &t->entry[index];
	}

	/* index is outside array's bounds - return null pointer */
	return 0;

} // entryat

/* push a new table-scope
 */
void symtable_push() {

	// new table
	struct symtable table;
	table.lastentry = -1;
	table.parent = -1;
	table.index = symtable_array.elements;

	// add new table to array and refresh pointers
	dd_da_add(&symtable_array, &table);
	if (symtable) symtable = dd_da_get(&symtable_array, current_symtable);
	current_symtable = symtable_array.elements-1;
	struct symtable *t = dd_da_get(&symtable_array, symtable_array.elements-1);
	if (!t) {
		printf("error getting symtable\n");
		return;
	}
	if (symtable) {
		t->parent = symtable->index;
	}
	symtable = t;
}

void symtable_pop() {
	if (symtable->parent >= 0) {
		symtable = dd_da_get(&symtable_array, symtable->parent);
		current_symtable = symtable->index;
	}
}
